﻿using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WordNet.Core;
using Moq;
using WordNet.Core.ElementContracts;
using WordNet.Core.DataContract;
using System.Collections.Generic;

namespace WordNet.CoreTests.Dictionary
{
    [TestClass]
    public class UnitTestDictionary
    {
        [TestMethod]
        public void DictionaryGetIndexWordTest()
        {
            // mock index word id
            var idIndex = new Mock<IIndexWordID>();

            // mock index word 
            var indexWordMock = new Mock<IIndexWord>();
            indexWordMock.Setup(wm => wm.Lemma).Returns("test");
            indexWordMock.Setup(wm => wm.PartOfSpeech).Returns(POS.NOUN);

            // mock data context
            var dataContextMock = new Mock<IDataContext>();
            dataContextMock.Setup(dc => dc.GetItem<IIndexWord>(idIndex.Object))
                .Returns(indexWordMock.Object).Verifiable("GetIndexWord");


            // create dictionary
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var result = dic.GetIndexWord(idIndex.Object);
            dataContextMock.Verify();
            
            Assert.AreEqual("test", result.Lemma);
            Assert.AreEqual(POS.NOUN, result.PartOfSpeech);
            
        }

        [TestMethod]
        public void DictionaryGetIndexWordListTest()
        {
            var dataContextMock = new Mock<IDataContext>();

            dataContextMock.Setup(dc => dc.GetAll<IIndexWord>(POS.NOUN))
                .Returns(new List<IIndexWord>()).Verifiable("GetAllIndexWords");

            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var list = dic.GetAllIndexWords(POS.NOUN).ToArray();
            dataContextMock.Verify();
            Assert.IsNotNull(list);
        }

        [TestMethod]
        public void DictionaryGetSynsetTest()
        {
            var synset = new Mock<ISynset>();
            synset.Setup(s => s.Offset).Returns(1111);
            synset.Setup(s => s.Gloss).Returns("test");

            var synsetId = new Mock<ISynsetID>();

            var dataContextMock = new Mock<IDataContext>();
            dataContextMock.Setup(dc => dc.GetItem<ISynset>(synsetId.Object))
                .Returns(synset.Object).Verifiable("GetSynset");

            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var result = dic.GetSynset(synsetId.Object);
            dataContextMock.Verify();

            Assert.AreEqual("test", result.Gloss);
            Assert.AreEqual(1111, result.Offset);

        }

        [TestMethod]
        public void DictionaryGetSynsetAllTest()
        {
            var dataContextMock = new Mock<IDataContext>();
            dataContextMock.Setup(dc => dc.GetAll<ISynset>(POS.NOUN))
                .Returns(new List<ISynset>()).Verifiable("GetAllSynsets");

            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var resultList = dic.GetAllSynsets(POS.NOUN).ToList<ISynset>();
            dataContextMock.Verify();

            Assert.IsNotNull(resultList);
        }

        [TestMethod]
        public void DictinaryGetSenseEntryTest()
        {
            var senseEntry = new Mock<ISenseEntry>();
            senseEntry.Setup(se => se.Offset).Returns(1111);

            var senseKey = new Mock<ISenseKey>();
            var dataContextMock = new Mock<IDataContext>();
            dataContextMock.Setup(dc => dc.GetItem<ISenseEntry>(senseKey.Object))
                .Returns(senseEntry.Object).Verifiable("GetSenseEntry");
           
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var result = dic.GetSenseEntry(senseKey.Object);

            dataContextMock.Verify();
            Assert.AreEqual(1111, result.Offset);
        }

        [TestMethod]
        public void DictionaryGetWordFromSynsetTest()
        {
            // mock sense key parameter
            var senseKey = new Mock<ISenseKey>();

            // mock sense entry reterned from call to getSenseEntry
            var senseEntry = new Mock<ISenseEntry>();
            senseEntry.Setup(se => se.Offset).Returns(1608019);
            senseEntry.Setup(se => se.PartOfSpeech).Returns(POS.NOUN);

            // mock data context
            var dataContextMock = new Mock<IDataContext>();
            // setup getSenseEntry
            dataContextMock.Setup(dc => dc.GetItem<ISenseEntry>(senseKey.Object))
                .Returns(senseEntry.Object)
                .Verifiable("getSenseEntry");

            // mock synset object returned from call to getSynset 
            var synset = new Mock<ISynset>();

            // mock word object contained by synset object
            var wordMock = new Mock<IWord>();
            wordMock.Setup(wm => wm.Lemma).Returns("test");
            wordMock.Setup(wm => wm.SenseKey).Returns(senseKey.Object);
            synset.Setup(sy => sy.GetWords()).Returns(new List<IWord>() { wordMock.Object });

            dataContextMock.Setup(dc => dc.GetItem<ISynset>(new SynsetID(1608019, POS.NOUN)))
                .Returns(synset.Object)
                .Verifiable("getSynset");

            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var result = dic.GetWord(senseKey.Object);
            dataContextMock.Verify();
            Assert.AreEqual("test", result.Lemma);
        }

        [TestMethod]
        public void DictionaryGetWordReturnsNullWhenSynsetIsNullTest()
        {
            WordID wid = new WordID(1608019, POS.NOUN, 1);
            var synsetId = wid.SynsetID;
            ISynset synsetNull = null;
            // mock data context
            var dataContextMock = new Mock<IDataContext>();
            dataContextMock.Setup(dc => dc.GetItem<ISynset>(synsetId))
               .Returns(synsetNull)
               .Verifiable("GetSynset");

            // run test
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var word = dic.GetWord(wid);
            dataContextMock.Verify();
            Assert.IsNull(word);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void DictionaryGetWordThrowsErrorTest()
        {
            // throws error when word number is less than one and 
            // lemma is null
            WordID wid = new WordID(1608019, POS.NOUN, 0);
            GetWordBody(wid);
        }

        [TestMethod]
        public void DictionaryGetWordWithWordIDCanFindWordForLemmaTest()
        {
            WordID wid = new WordID(1608019, POS.NOUN, "dog");
            GetWordBody(wid);
        }

        [TestMethod]
        public void DictionaryGetWordWithWordIDCanFindWordForWordNumberTest()
        {
            WordID wid = new WordID(1608019, POS.NOUN, 1);
            GetWordBody(wid);
        }
        
        private void GetWordBody(IWordID wid)
        {
            // mock word object
            var wordMock = new Mock<IWord>();
            wordMock.Setup(wm => wm.Lemma).Returns("dog");
            wordMock.Setup(wm => wm.ID).Returns(wid);

            var wordMockSecond = new Mock<IWord>();
            wordMockSecond.Setup(wm => wm.Lemma).Returns("Second");

            // mock synset object returned from call to GetSynset
            var synsetMock = new Mock<ISynset>();
            synsetMock.Setup(sm => sm.GetWords())
                .Returns(new List<IWord>() { wordMock.Object, wordMockSecond.Object })
                .Verifiable("GetWords");

            // mock data context
            var dataContextMock = new Mock<IDataContext>();

            // mock call to GetSynset
            var synsetId = wid.SynsetID;
            dataContextMock.Setup(dc => dc.GetItem<ISynset>(synsetId))
                .Returns(synsetMock.Object)
                .Verifiable("GetSynset");

            // run test
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            var word = dic.GetWord(wid);
            dataContextMock.Verify();
            Assert.AreEqual("dog", word.Lemma);
            Assert.AreEqual(POS.NOUN.Name, word.ID.PartOfSpeech.Name);
            Assert.AreEqual(1608019, word.ID.SynsetID.Offset);

        }

        [TestMethod]
        public void GetWordAlternativeApproacheTest()
        {
            var senseKeyMock = new Mock<ISenseKey>();
            senseKeyMock.Setup(sk => sk.Lemma).Returns("test");
            senseKeyMock.Setup(sk => sk.PartOfSpeech).Returns(POS.NOUN);

            var dataContextMock = new Mock<IDataContext>();

            // mock GetSenseEntry
            var senseEntryMock = new Mock<ISenseEntry>();
            senseEntryMock.Setup(se => se.Offset).Returns(1111);
            senseEntryMock.Setup(se => se.PartOfSpeech).Returns(POS.NOUN);
            dataContextMock.Setup(dc => dc.GetItem<ISenseEntry>(senseKeyMock.Object))
                .Returns(senseEntryMock.Object)
                .Verifiable("GetSenseEntry");

            // mock GetSynset
            ISynsetID id = new SynsetID(senseEntryMock.Object.Offset, senseEntryMock.Object.PartOfSpeech);
            ISynset synsetNull = null; // it enables the alterantive approache
            dataContextMock.Setup(dc => dc.GetItem<ISynset>(id))
                .Returns(synsetNull)
                .Verifiable("GetSynset");

            // GetIndexWord
            var indexWordMock = new Mock<IIndexWord>();
            indexWordMock.Setup(iw => iw.GetWordIDs())
                .Returns(new List<IWordID>() { new WordID(11111, POS.NOUN, 1), new WordID(11112, POS.NOUN, 2) });
            IIndexWordID indexWordId = new IndexWordID(senseKeyMock.Object.Lemma, senseKeyMock.Object.PartOfSpeech);
            dataContextMock.Setup(dc => dc.GetItem<IIndexWord>(indexWordId))
                .Returns(indexWordMock.Object)
                .Verifiable("GetIndexWord");

            // mock GetWord
            IWordID wid = new WordID(11111, POS.NOUN, 1);
            var wordMock = new Mock<IWord>();
            wordMock.Setup(wm => wm.Lemma).Returns("test");
            wordMock.Setup(wm => wm.ID).Returns(wid);
            var synsetMock = new Mock<ISynset>();
            synsetMock.Setup(sm => sm.GetWords()).Returns(new List<IWord> { wordMock.Object });
            wordMock.Setup(wm => wm.Synset).Returns(synsetMock.Object);
            var synsetId = wid.SynsetID;

            synsetMock.Setup(sm => sm.GetWords())
                .Returns(new List<IWord>() { wordMock.Object })
                .Verifiable("GetWords");

            dataContextMock.Setup(dc => dc.GetItem<ISynset>(synsetId))
                .Returns(synsetMock.Object)
                .Verifiable("GetSynset");

            // run test
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            IWord result = dic.GetWord(senseKeyMock.Object);
            synsetMock.Verify();
            dataContextMock.Verify();
            Assert.AreEqual("test", result.Lemma);

        }
       
        
        [TestMethod]
        public void GetWordAlternativeApproacheReturnsNullTest()
        {
            var senseKeyMock = new Mock<ISenseKey>();
            senseKeyMock.Setup(sk => sk.Lemma).Returns("test");
            senseKeyMock.Setup(sk => sk.PartOfSpeech).Returns(POS.NOUN);

            var dataContextMock = new Mock<IDataContext>();

            // mock GetSenseEntry
            var senseEntryMock = new Mock<ISenseEntry>();
            senseEntryMock.Setup(se => se.Offset).Returns(1111);
            senseEntryMock.Setup(se => se.PartOfSpeech).Returns(POS.NOUN);
            dataContextMock.Setup(dc => dc.GetItem<ISenseEntry>(senseKeyMock.Object))
                .Returns(senseEntryMock.Object)
                .Verifiable("GetSenseEntry");

            // mock GetSynset
            ISynsetID id = new SynsetID(senseEntryMock.Object.Offset, senseEntryMock.Object.PartOfSpeech);
            ISynset synsetNull = null; // it enables the alterantive approache
            dataContextMock.Setup(dc => dc.GetItem<ISynset>(id))
                .Returns(synsetNull)
                .Verifiable("GetSynset");

            // GetIndexWord
            IIndexWord indexWordNull = null;
            IIndexWordID indexWordId = new IndexWordID("test", POS.NOUN);
            dataContextMock.Setup(dc => dc.GetItem<IIndexWord>(indexWordId))
                .Returns(indexWordNull)
               .Verifiable("GetIndexWord");

            // run test
            WNDictionary dic = new WNDictionary(dataContextMock.Object);
            IWord result = dic.GetWord(senseKeyMock.Object);
            dataContextMock.Verify();
            Assert.IsNull(result);

        }

    }
}
